package com.android.kvdb;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.SQLException;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQuery;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.util.Log;

import androidx.annotation.Keep;
import androidx.collection.ArrayMap;
import androidx.collection.ArraySet;

import java.io.File;
import java.util.Map;

import static android.os.Build.VERSION.SDK_INT;

/**
 * @author GYL
 * @dateTime 2020/5/11-13:36
 * @description
 */
public class KVDB {

    private static final String TAG = "KVDB";
    private static volatile KVDB singleton = null;

    private static final String DATABASE_NAME = "kv.db";
    private static final String DATABASE_TABLE = "tb_kv";
    private static int DATABASE_VERSION = 1;

    private static final String KEY = "KEY";
    private static final String VALUE = "VALUE";
    private static final String PERSIST = "PERSIST";
    private static final String KEY_CREATED_AT = "KEY_CREATED_AT";
    private static final String SQL_TABLE_SELECT = "SELECT count(*)  FROM sqlite_master WHERE type='table' AND name = ? ;";

    //private AtomicInteger mOpenCounter = new AtomicInteger();

    private DBHelper dbHelper;
    private Context mContext;
    private String dbName;
    private String tableName;
    private int dbVersion;

    private SQLiteDatabase mDatabase;

    private SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
        @Override
        public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) {
            //Log.i(TAG, query.toString());
            if (SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                return new SQLiteCursor(masterQuery, editTable, query);
            } else {
                return new SQLiteCursor(db, masterQuery, editTable, query);
            }
        }
    };


    public static final class Builder {
        private Context mContext;
        private String dbName = DATABASE_NAME;
        private String dbTable = DATABASE_TABLE;
        private int dbVersion = DATABASE_VERSION;
        private String dbPath = null;

        @Keep
        public Builder(Context context) {
            this.mContext = context;
        }

        @Keep
        public Builder withDBName(String dbName) {
            this.dbName = dbName;
            return this;
        }

        @Keep
        public Builder withTableName(String dbTable) {
            this.dbTable = dbTable;
            return this;
        }

        @Keep
        public Builder withDbVersion(int dbVersion) {
            this.dbVersion = dbVersion;
            return this;
        }

        /**
         * 设置db保存位置
         *
         * @param dbPath
         * @return
         */
        @Keep
        public Builder withDbPath(String dbPath) {
            this.dbPath = dbPath;
            return this;
        }

        private String generateDBName() {
            boolean isSdcardEnable = false;
            String state = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(state)) {
                //SDCard是否插入
                isSdcardEnable = true;
            }
            if (isSdcardEnable) {
                try {
                    File dbPathFile = new File(dbPath);
                    if (!dbPathFile.exists()) {
                        dbPathFile.mkdirs();
                    }
                    return dbPathFile.getAbsolutePath() + File.separator + dbName;
                } catch (Exception e) {
                    KVDB.log("DB 文件目录创建异常：" + e.getMessage());
                }
            }
            return dbName;
        }

        @Keep
        public KVDB build() {
            KVDB kvdb;
            if (dbPath != null && !"".equals(dbPath)) {
                kvdb = new KVDB(mContext, generateDBName(), dbTable, dbVersion);
            } else {
                kvdb = new KVDB(mContext, dbName, dbTable, dbVersion);
            }
            return kvdb;
        }
    }

    private synchronized SQLiteDatabase getWritableDatabase() {
        /*if (mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = dbHelper.getWritableDatabase();
        }*/
        if (mDatabase == null || !mDatabase.isOpen()) {
            //open new write database
            mDatabase = dbHelper.getWritableDatabase();
        }

        log("open database.");
        return mDatabase;
    }

    private synchronized void closeDatabase() {
       /* if (mOpenCounter.decrementAndGet() == 0) {
            // Closing database
            mDatabase.close();
        }*/
        if (mDatabase != null && mDatabase.isOpen()) {
            //closing database
            mDatabase.close();
        }
        log("close database.");
    }

    private static String createTableSql(String table) {
        String sql = "CREATE TABLE %s ( %s TEXT PRIMARY KEY , %s TEXT , %s INTEGER , %s INTEGER);";
        return String.format(sql, table, KEY, VALUE, PERSIST, KEY_CREATED_AT);
    }

    private static String createDeleteSql(String table, long count, long limit) {
        String sql = "DELETE FROM %s WHERE %s IN (SELECT %s FROM %s WHERE %s = 0 ORDER BY %s ASC LIMIT %s );";
        return String.format(sql, table, KEY, KEY, DATABASE_TABLE, PERSIST, KEY_CREATED_AT, String.valueOf(count - limit));
    }

    private static String createDeleteByKeySql(String table, String key) {
        return String.format("DELETE FROM %s WHERE %s = '%s';", table, KEY, key);
    }

    private static String deleteAllSql(String table) {
        return String.format("DELETE FROM %s;", table);
    }

    /**
     * Set the  instance
     *
     * @param context
     * @return
     */
    @Keep
    public static KVDB getInstance(Context context) {
        // Use the application context, which will ensure that you
        // don't accidentally leak an Activity's context.
        // See this article for more information: http://bit.ly/6LRzfx
        if (singleton == null) {
            synchronized (KVDB.class) {
                if (singleton == null) {
                    singleton = new Builder(context).build();
                }
            }
        }
        return singleton;
    }

    /**
     * Set the global instance returned from {@link #getInstance}.
     * <p>
     * This method must be called before any calls to {@link #getInstance} and may only be called once.
     */
    @Keep
    public static void setSingletonInstance(KVDB kvdb) {
        synchronized (KVDB.class) {
            if (singleton != null) {
                throw new IllegalStateException("Singleton instance already exists.");
            }
            singleton = kvdb;
        }
    }

    /**
     * Constructor should be private to prevent direct instantiation.
     * make call to static method "getInstance()" instead.
     *
     * @param context Any context object.
     */
    private KVDB(Context context, String dbName, String dbTable, int dbVersion) {
        this.mContext = context;
        this.dbName = dbName;
        this.dbVersion = dbVersion;
        this.tableName = dbTable;
        this.init();
    }

    private void init() {
        DatabaseErrorHandler errorHandler = null;
        if (SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            errorHandler = new DatabaseErrorHandler() {
                @Override
                public void onCorruption(SQLiteDatabase dbObj) {
                    dbObj.close();
                }
            };
        }
        if (SDK_INT > Build.VERSION_CODES.O_MR1) {
            SQLiteDatabase.OpenParams openParams = new SQLiteDatabase.OpenParams.Builder()
                    .setCursorFactory(factory)
                    .setErrorHandler(errorHandler)
                    .addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY)
                    .build();
            this.dbHelper = new DBHelper(mContext, dbName, dbVersion, openParams);
        } else {
            this.dbHelper = new DBHelper(mContext, dbName, factory, dbVersion, errorHandler);
        }
    }

    /**
     * @author CTKJ-0224
     * @dateTime 2020/5/12-10:26
     * @description sqliteHelper
     */
    private final class DBHelper extends SQLiteOpenHelper {

        /**
         * @param context
         * @param name
         * @param factory
         * @param version
         * @see SQLiteDatabase#openDatabase(String, SQLiteDatabase.CursorFactory, int)
         */
        public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }

        /**
         * @param context
         * @param name
         * @param factory
         * @param version
         * @param errorHandler
         * @see SQLiteDatabase#openDatabase(String, SQLiteDatabase.CursorFactory, int, DatabaseErrorHandler)
         */
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
            super(context, name, factory, version, errorHandler);
        }

        /**
         * @param context
         * @param name
         * @param version
         * @param openParams
         * @see SQLiteDatabase#openDatabase(File, SQLiteDatabase.OpenParams)
         */
        @TargetApi(Build.VERSION_CODES.P)
        public DBHelper(Context context, String name, int version, SQLiteDatabase.OpenParams openParams) {
            super(context, name, version, openParams);
        }

        /**
         * 创建数据文件时调用，此时适合创建新表
         *
         * @param db
         */
        @Override
        public void onCreate(SQLiteDatabase db) {
            //String CREATE_SQL = "create table user( id INTEGER primary key autoincrement,name text,age INTEGER)";
            //Log.d(TAG, "create sql : " + CREATE_SQL);
            //db.execSQL(CREATE_SQL);
            flush(db);
        }

        /**
         * 更新数据库版本时调用，适合更新表结构或创建新表
         *
         * @param db
         * @param oldVersion
         * @param newVersion
         */
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        }

        /**
         * Below are the private DB creating methods.
         *
         * @param e
         * @param db
         */
        public void flush(Exception e, SQLiteDatabase db) {
            //e.printStackTrace();
            log(e.getMessage());
            flush(db);
        }

        public void flush(SQLiteDatabase db) {
            try {
                //db.execSQL("DROP TABLE IF EXISTS " + dbTable);
                if (!existsTable(db, tableName)) {
                    //不存在创建表
                    db.execSQL(createTableSql(tableName));
                }
            } catch (SQLException e) {
                KVDB.log("flush db exception : " + e.getMessage());
                throw new IllegalStateException("SQLException while flush. Have to drop caching");
            } finally {
            }
        }

        boolean existsTable(SQLiteDatabase db, final String tableName) {
            //"SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?",
            Cursor cursor = null;
            try {
                cursor = db.rawQuery(SQL_TABLE_SELECT, new String[]{tableName});
                if (cursor == null || cursor.getCount() == 0) {
                    return false;
                }
                cursor.moveToFirst();
                return cursor.getInt(0) > 0;
            } catch (Exception e) {
                return false;
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }
    }

    /**
     * Setter method. Sets a (key, value) pair in sqlite3 db.
     *
     * @param context Any context object.
     * @param key     The URL or some other unique id for data can be used
     * @param value   String data to be saved
     * @param persist Whether to delete this (key, value, time, persist) tuple, when cleaning cache in
     *                clearCacheByLimit() method. 1 Means persist, 0 Means remove.
     * @return rowid of the insertion row
     */
    private synchronized long set(Context context, String key, String value, Boolean persist) {
        //key = DatabaseUtils.sqlEscapeString(key);
        KVDB.log("setting data :  key=" + key + " value=" + value);
        SQLiteDatabase db = getWritableDatabase();
        long row = 0;
        if (db != null) {

            ContentValues values = new ContentValues();
            values.put(KEY, key);
            values.put(VALUE, value == null ? "" : value);
            // Wrapper of Boolean around 0, 1. This was original architecture.
            // Boolean persist was added for aesthetics
            if (persist) {
                values.put(PERSIST, 1);
            } else {
                values.put(PERSIST, 0);
            }
            //"time('now')"
            values.put(KEY_CREATED_AT, System.currentTimeMillis());
            Cursor c = null;
            try {
                row = db.replace(DATABASE_TABLE, null, values);
            } catch (SQLiteException e) {
                dbHelper.flush(e, db);
                set(context, key, value, persist);
            }
            //db.close();
            closeDatabase();
        }
        return row;
    }

    /**
     * Executes the task with the specified parameters,
     * allowing multiple tasks to run in parallel on a pool of threads managed by {@link AsyncTask}.
     *
     * @param task The {@link AsyncTask} to execute.
     */
    private static <Params, Progress, Result> void executeParallel(AsyncTask<Params, Void, Result> task, Params... params) {
        if (task == null) {
            throw new IllegalArgumentException("task can not be null");
        }
        if (SDK_INT >= 11) {
            // From API 11 onwards, we need to manually select the THREAD_POOL_EXECUTOR
            //AsyncTaskCompatHoneycomb.executeParallel(task, params);
            //调用 THREAD_POOL_EXECUTOR，使用该线程池
            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
        } else {
            // Before API 11, all tasks were run in parallel
            task.execute(params);
        }
    }


    /**
     * async Setter method. Sets a (key, value) pair in sqlite3 db.
     *
     * @param context
     * @param key
     * @param value
     * @param persist
     */
    @SuppressLint({"StaticFieldLeak", "DefaultLocale"})
    private synchronized void asyncSet(final Context context, final String key, final String value, final Boolean persist) {
        executeParallel(new AsyncTask<Void, Void, Long>() {
            @Override
            protected Long doInBackground(Void... voids) {
                return singleton.set(context, key, value, persist);
            }

            @Override
            protected void onPostExecute(Long aLong) {
                log(String.format("set key=%s ,value=%s , result=%d", key, value, aLong));
            }
        });
    }

    /**
     * 异步获取数据
     *
     * @param context
     * @param key
     * @param defaultValue
     * @param sqliteCallback
     */
    @SuppressLint("StaticFieldLeak")
    public static void asyncGet(final Context context, final String key, final String defaultValue, final SqliteCallback sqliteCallback) {
        executeParallel(new AsyncTask<Void, Void, Object>() {
            @Override
            protected Object doInBackground(Void... voids) {
                String s = singleton.get(context, key, defaultValue);
                return s;
            }

            @Override
            protected void onPostExecute(Object o) {
                if (sqliteCallback != null) {
                    sqliteCallback.onSqlCallback(key, o);
                }
            }
        });
    }


    /**
     * Getter method. Gets a value based on it's key pair in sqlite3 db.
     *
     * @param context      Any context object.
     * @param key          The URL or some other unique id for data can be used
     * @param defaultValue value to be returned in case something goes wrong or no data is found
     * @return value stored in DB if present, defaultValue otherwise.
     */
    private synchronized String get(Context context, String key, String defaultValue) {
        //key = DatabaseUtils.sqlEscapeString(key);
        KVDB.log("getting data :  key=" + key);
        SQLiteDatabase db = getWritableDatabase();
        String value = defaultValue;
        if (db != null) {
            Cursor c = null;
            try {
                c = db.query(DATABASE_TABLE, new String[]{VALUE}, KEY + "=?", new String[]{key}, null, null, null);
            } catch (SQLiteException e) {
                dbHelper.flush(e, db);
                get(context, key, defaultValue);
            }
            if (c != null) {
                if (c.moveToNext()) {
                    value = c.getString(c.getColumnIndex(VALUE));
                }
                KVDB.log("getting data :  value=" + value);
                c.close();
            }
            //db.close();
            closeDatabase();
        }
        return value;
    }

    /**
     * Clear the cache like a FIFO queue defined by the limit parameter.
     * Each function call made to this will remove count(*)-limit first rows from the DB
     * Only the data with (Persist, 0) will be removed
     *
     * @param context Any context object.
     * @param limit   amount of data to be retained in FIFO, rest would be removed like a queue
     * @return number of rows affected on success
     */
    @Keep
    public static synchronized long clearByLimit(Context context, long limit) {
        KVDB kvdb = getInstance(context);
        SQLiteDatabase db = kvdb.getWritableDatabase();//kvdb.dbHelper.getWritableDatabase();
        long numRows = 0;
        if (db != null) {
            Cursor c = null;
            try {
                c = db.query(DATABASE_TABLE, null, null, null, null, null, null);
            } catch (SQLiteException e) {
                kvdb.dbHelper.flush(e, db);
                clearByLimit(context, limit);
            }
            if (c != null) {
                long count = c.getCount();
                //Log.v(TAG, "cached rows" + String.valueOf(count));
                if (count > limit) {
                    try {
                        db.execSQL(createDeleteSql(DATABASE_TABLE, count, limit));
                    } catch (SQLiteException e) {
                        kvdb.dbHelper.flush(e, db);
                        clearByLimit(context, limit);
                    }
                }
                try {
                    c = db.query(DATABASE_TABLE, null, null, null, null, null, null);
                } catch (SQLiteException e) {
                    kvdb.dbHelper.flush(e, db);
                    clearByLimit(context, limit);
                }
                numRows = count - c.getCount();
                c.close();
            }
            //db.close();
            kvdb.closeDatabase();
        }
        return numRows;
    }

    @Keep
    public static long setString(Context context, String key, String value) {
        return getInstance(context).set(context, key, value, false);
    }

    @Keep
    public static String getString(Context context, String key) {
        return getString(context, key, "");
    }

    @Keep
    public static String getString(Context context, String key, String def) {
        return getInstance(context).get(context, key, def);
    }

    @Keep
    public static long setInteger(Context context, String key, Integer value) {
        return getInstance(context).set(context, key, String.valueOf(value), false);
    }

    @Keep
    public static long setInteger(Context context, String key, Integer value, Boolean persist) {
        return getInstance(context).set(context, key, String.valueOf(value), persist);
    }

    @Keep
    public static Integer getInteger(Context context, String key) {
        try {
            return Integer.parseInt(getInstance(context).get(context, key, null));
        } catch (Exception e) {
            return -1;
        }
    }

    @Keep
    public static Integer getInteger(Context context, String key, Integer defaultValue) {
        try {
            return Integer.parseInt(getInstance(context).get(context, key, String.valueOf(defaultValue)));
        } catch (Exception e) {
            return defaultValue;
        }
    }

    @Keep
    public static long setFloat(Context context, String key, Float value) {
        return getInstance(context).set(context, key, String.valueOf(value), false);
    }

    @Keep
    public static long setFloat(Context context, String key, Float value, Boolean persist) {
        return getInstance(context).set(context, key, String.valueOf(value), persist);
    }

    @Keep
    public static Float getFloat(Context context, String key) {
        try {
            return Float.parseFloat(getInstance(context).get(context, key, null));
        } catch (Exception e) {
            return -1f;
        }
    }

    @Keep
    public static Float getFloat(Context context, String key, Float defaultValue) {
        try {
            return Float.parseFloat(getInstance(context).get(context, key, String.valueOf(defaultValue)));
        } catch (Exception e) {
            return defaultValue;
        }
    }

    @Keep
    public static long setDouble(Context context, String key, Double value) {
        return getInstance(context).set(context, key, String.valueOf(value), false);
    }

    @Keep
    public static long setDouble(Context context, String key, Double value, Boolean persist) {
        return getInstance(context).set(context, key, String.valueOf(value), persist);
    }

    @Keep
    public static Double getDouble(Context context, String key, Double defaultValue) {
        try {
            return Double.parseDouble(getInstance(context).get(context, key, String.valueOf(defaultValue)));
        } catch (Exception e) {
            return defaultValue;
        }

    }

    @Keep
    public static long setBoolean(Context context, String key, Boolean value) {
        return getInstance(context).set(context, key, String.valueOf(value), false);
    }

    @Keep
    public static long setBoolean(Context context, String key, Boolean value, Boolean persist) {
        return getInstance(context).set(context, key, String.valueOf(value), persist);
    }

    @Keep
    public static Boolean getBoolean(Context context, String key, Boolean defaultValue) {
        try {
            return Boolean.parseBoolean(getInstance(context).get(context, key, String.valueOf(defaultValue)));
        } catch (Exception e) {
            return defaultValue;
        }

    }

    @Keep
    public static long setByte(Context context, String key, Byte value) {
        return getInstance(context).set(context, key, String.valueOf(value), false);
    }

    @Keep
    public static long setByte(Context context, String key, Byte value, Boolean persist) {
        return getInstance(context).set(context, key, String.valueOf(value), persist);
    }

    @Keep
    public static Byte getByte(Context context, String key, Byte defaultValue) {
        try {
            return Byte.parseByte(getInstance(context).get(context, key, String.valueOf(defaultValue)));
        } catch (Exception e) {
            return defaultValue;
        }

    }

    @Keep
    public static long setLong(Context context, String key, Long value) {
        return getInstance(context).set(context, key, String.valueOf(value), false);
    }

    @Keep
    public static long setLong(Context context, String key, Long value, Boolean persist) {
        return getInstance(context).set(context, key, String.valueOf(value), persist);
    }

    @Keep
    public static Long getLong(Context context, String key, Long defaultValue) {
        try {
            return Long.parseLong(getInstance(context).get(context, key, String.valueOf(defaultValue)));
        } catch (Exception e) {
            return defaultValue;
        }

    }

    @Keep
    public static long setShort(Context context, String key, Short value) {
        return getInstance(context).set(context, key, String.valueOf(value), false);
    }

    @Keep
    public static long setShort(Context context, String key, Short value, Boolean persist) {
        return getInstance(context).set(context, key, String.valueOf(value), persist);
    }

    @Keep
    public static Short getShort(Context context, String key, Short defaultValue) {
        try {
            return Short.parseShort(getInstance(context).get(context, key, String.valueOf(defaultValue)));
        } catch (Exception e) {
            return defaultValue;
        }
    }

    @Keep
    public static boolean remove(Context context, String key) {
        log("delete key " + key);
        KVDB kvdb = getInstance(context);
        SQLiteDatabase db = kvdb.getWritableDatabase();
        if (db != null) {
            try {
                db.execSQL(createDeleteByKeySql(DATABASE_TABLE, key));
                return true;
            } catch (SQLiteException e) {
                kvdb.dbHelper.flush(e, db);
            }
            //db.close();
            kvdb.closeDatabase();
        }
        return false;
    }

    @Keep
    public static boolean removeAll(Context context) {
        log("delete all ");
        KVDB kvdb = getInstance(context);
        SQLiteDatabase db = kvdb.getWritableDatabase();
        if (db != null) {
            try {
                db.execSQL(deleteAllSql(DATABASE_TABLE));
                return true;
            } catch (SQLiteException e) {
                kvdb.dbHelper.flush(e, db);
            }
            //db.close();
            kvdb.closeDatabase();
        }
        return false;
    }


    private SqlEditor mSqlEditor;


    @Keep
    public static SqlEditor editor(Context context) {
        return getInstance(context).newEditor();
    }

    private synchronized SqlEditor newEditor() {
        if (mSqlEditor != null) {
            mSqlEditor.clear();
            return mSqlEditor;
        } else {
            mSqlEditor = new SqlEditor();
        }
        return mSqlEditor;
    }

    public final class SqlEditor {
        private ArrayMap<String, String> arrMap;
        private ArraySet<String> removeKeys;

        private SqlEditor() {
            arrMap = new ArrayMap<>();
            removeKeys = new ArraySet<>();
        }

        public SqlEditor putString(String key, String value) {
            arrMap.put(key, value);
            return this;
        }


        public SqlEditor putInt(String key, int value) {
            arrMap.put(key, String.valueOf(value));
            return this;
        }

        public SqlEditor putLong(String key, long value) {
            arrMap.put(key, String.valueOf(value));
            return this;
        }

        public SqlEditor putFloat(String key, float value) {
            arrMap.put(key, String.valueOf(value));
            return this;
        }

        public SqlEditor putBoolean(String key, boolean value) {
            arrMap.put(key, String.valueOf(value));
            return this;
        }

        public SqlEditor remove(String key) {
            removeKeys.add(key);
            return this;
        }

        private void clear() {
            if (arrMap != null) {
                arrMap.clear();
            }
            if (removeKeys != null) {
                removeKeys.clear();
            }
        }

        public void commit() {
            if (singleton != null) {
                singleton.apply();
            }
        }

    }

    @SuppressLint("StaticFieldLeak")
    private void apply() {
        if (mSqlEditor != null) {
            executeParallel(new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... voids) {
                    SQLiteDatabase db = getWritableDatabase();
                    for (Map.Entry<String, String> entry : mSqlEditor.arrMap.entrySet()) {
                        //SQLiteStatement sqLiteStatement=db.compileStatement()
                        if (db != null) {
                            ContentValues values = new ContentValues();
                            values.put(KEY, entry.getKey());
                            values.put(VALUE, entry.getValue() == null ? "" : entry.getValue());
                            values.put(PERSIST, 0);
                            values.put(KEY_CREATED_AT, System.currentTimeMillis());
                            Cursor c = null;
                            try {
                                db.replace(DATABASE_TABLE, null, values);
                            } catch (SQLiteException e) {
                                dbHelper.flush(e, db);
                                try {
                                    db.replace(DATABASE_TABLE, null, values);
                                } catch (Exception es) {
                                    es.printStackTrace();
                                }
                            }
                        }
                    }
                    //删除
                    for (String key : mSqlEditor.removeKeys) {
                        if (db != null) {
                            try {
                                db.execSQL(createDeleteByKeySql(DATABASE_TABLE, key));
                            } catch (Exception e) {
                                dbHelper.flush(e, db);
                                try {
                                    db.execSQL(createDeleteByKeySql(DATABASE_TABLE, key));
                                } catch (Exception ee) {
                                }
                            }
                        }
                    }
                    //db.close();
                    closeDatabase();
                    mSqlEditor.clear();
                    mSqlEditor=null;
                    return null;
                }
            });
        }
    }

    @Keep
    public static void setDebug(boolean debug) {
        isDebug = debug;
    }

    private static boolean isDebug = false;

    public static void log(String msg) {
        if (isDebug) {
            Log.w("KVDB", msg);
        }
    }


    public interface SqliteCallback {
        void onSqlCallback(String key, Object value);
    }

}
